import { createContext, useContext, useEffect, useState } from 'react'
import { bindActionCreators } from 'redux'
import PT from 'prop-types'
import _ from '@/assets/utils'

/* 创建上下文对象 */
const ThemeContext = createContext()

/* Provider：把基于属性传递进来的store对象，放在上下文中 */
export const Provider = function Provider(props) {
    let { store, children } = props
    return <ThemeContext.Provider
        value={{
            store
        }}>
        {children}
    </ThemeContext.Provider>
}
Provider.propTypes = {
    store: PT.object.isRequired
}

/* 提供三个自定义Hook函数 */
export const useStore = function useStore() {
    let { store } = useContext(ThemeContext)
    return store
}
export const useDispatch = function useDispatch() {
    const store = useStore()
    return store.dispatch
}
export const useSelector = function useSelector(callback) {
    if (typeof callback !== 'function') throw new TypeError(`useSelector 中的 callback 必须是一个函数`)
    // 获取总状态
    const store = useStore(),
        state = store.getState()

    // 向redux事件池中，注入让组件更新的办法
    let [, setRandom] = useState(+new Date())
    useEffect(() => {
        let unsubscribe = store.subscribe(() => {
            // 其内部在让组件更新之前做了优化：对比新老状态（浅比较）,如果不一样，才让组件更新
            setRandom(+new Date())
        })
        return () => unsubscribe()
    }, [])

    // 把传递的 callback 函数执行，传递总状态，接收返回的对象(包含需要用的状态)
    let result = callback(state)
    if (!_.isPlainObject(result)) throw new TypeError(`callback 执行，必须返回的一个对象`)
    return result
}

/* 核心方法：connect */
export const connect = function connect(mapStateToProps, mapDispatchToProps) {
    // 处理参数为空的情况
    if (mapStateToProps == null) {
        mapStateToProps = () => {
            return {}
        }
    }
    if (mapDispatchToProps == null) {
        mapDispatchToProps = () => {
            return {}
        }
    }
    return function (Component) {
        // Component：最终需要渲染的组件
        // 还需要返回一个供外面调用的组件「HOC：Higher-Order Components 」
        return function HOC(props) {
            // 传递给HOC组件的属性，其实也是要传递给Vote的
            let attrs = {
                ...props
            }

            // 处理 mapStateToProps「直接用 useSelector 处理即可，其内部把事件池的操作都处理了」
            let state = useSelector(mapStateToProps)
            Object.assign(attrs, state)

            // 处理 mapDispatchToProps 
            let temp = {}
            let dispatch = useDispatch()
            if (typeof mapDispatchToProps === 'function') {
                // 是函数的情况：把函数执行，返回的信息就是需要基于属性传递给组件的
                temp = mapDispatchToProps(dispatch)
            } else if (_.isPlainObject(mapDispatchToProps)) {
                // 是对象的情况：说明其实 actionCreators 对象，我们需要基于 bindActionCreators 处理
                temp = bindActionCreators(mapDispatchToProps, dispatch)
            }
            if (!_.isPlainObject(temp)) throw new TypeError(`mapDispatchToProps 处理的结果需要是一个对象`)
            Object.assign(attrs, temp)

            // 但是最终要渲染的还是之前传递的组件（例如：Vote），并且把 mapStateToProps&mapDispatchToProps 等处理后的结果，通过属性传递给这个组件
            return <Component {...attrs} />
        }
    }
}